package com.xiaoxx.server;

import com.alibaba.fastjson.JSON;
import com.xiaoxx.model.entity.webSocket.OnlineUser;
import com.xiaoxx.model.vo.webSocket.SendMsgVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author wangsong
 * Websocket 监听类(连接，断点，消息发送等)
 * <p>
 * /websocket/{userId}/{username}   =  /websocket/用户Id/用户名 来连接websocket，该参数会带到每一个监听方法中
 * 同一个账号重复登录, 会被挤下线
 * </P>
 * @ServerEndpoint: socket链接地址
 */
@ServerEndpoint("/websocket/{userId}/{username}")
@Slf4j
@Component
public class WebsocketServer {
    private volatile static int onlineCount = 0;

    private final static Map<String, OnlineUser> CLIENTS = new ConcurrentHashMap<>();
    /**
     * 所有用户信息(session + userId + username + createTime  --> 以用户的id为key, 通过用户key来获取用户session进行消息发送)
     */
//    private final static Map<String, List<OnlineUser>> CLIENTS = new ConcurrentHashMap<>();

    /**
     * 监听连接（有用户连接，立马到来执行这个方法），session 发生变化
     *
     * @param userId   用户id
     * @param username 用户名
     * @param session  当前用户会话
     * @return void
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, @PathParam("username") String username, Session session) {
        String headPic = "https://mall--product.oss-cn-beijing.aliyuncs.com/oss/file/file/2023-11-06/qyy资质业务材料01.jpg";
        // 保存到当前服务器
        if (CLIENTS.containsKey(userId)) {
            // 保存已存在用户的 session 会话
            CLIENTS.put(userId, new OnlineUser(userId, username, session, headPic));
        } else {
            // 保存用户 session 会话
            CLIENTS.put(userId, new OnlineUser(userId, username, session, headPic));
        }
        onlineCount += 1;
        log.info("用户{}连接成功,当前在线人数为{}", userId, onlineCount);
        List<OnlineUser> onlineUserList = getOnlineUserList(userId);
        for (OnlineUser onlineUser : onlineUserList) {
            sendMsg(onlineUser.getUserId(), new SendMsgVO(userId, username, onlineUser.getUserId(), "《".concat(username).concat("》上线了"), "0", onlineUserList.size()));
        }
    }


    /**
     * 监听断开连接（有用户退出，会立马到来执行这个方法）
     *
     * @param userId   用户id
     * @param username 用户名
     * @param session  当前用户会话
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId, @PathParam("username") String username, Session session) {
        CLIENTS.remove(userId);
        onlineCount -= 1;
        List<OnlineUser> onlineUserList = getOnlineUserList(userId);
        for (OnlineUser onlineUser : onlineUserList) {
            sendMsg(onlineUser.getUserId(), new SendMsgVO(userId, username, onlineUser.getUserId(), "《".concat(username).concat("》下线了"), "1", onlineUserList.size()));
        }
        log.info("用户{}断开,当前在线人数为{}", userId, onlineCount <= 0 ? 0 : onlineCount);
    }


    /**
     * 异常停止
     *
     * @param userId   用户id
     * @param username 用户名
     * @param session  当前用户会话
     * @param e        异常信息
     */
    @OnError
    public void onError(@PathParam("userId") String userId, @PathParam("username") String username, @PathParam("headPic") String headPic, Session session, Throwable e) {
        log.info("服务端发生了错误" + e);
    }


    /**
     * 监听消息发送（收到客户端的消息立即执行）
     *
     * @param userId   用户id
     * @param username 用户名
     * @param message  传递的消息内容, json数据( to=接收人用户Id  (目标ID,逗号分隔) || content=内容  || content=消息类型)
     * @param session  当前用户会话
     *                 <p>
     *                 // 前端发送内容格式
     *                 ....
     *                 // 拼接参数
     *                 let message = {"content": "测试发送消息", "to": "ALL"}
     *                 ....
     */
    @OnMessage
    public void onMessage(@PathParam("userId") String userId, @PathParam("username") String username, String message, Session session) {
        String id = session.getId();
        log.info("收到用户{}信息----》{}", userId, message);
    }


    /**
     * 消息发送（ 遍历用户Id , 在通过sendMsg方法发送消息）
     * <p>
     * 提供给外部或当前监听消息发送消息
     * </P>
     *
     * @param sendMsg：消息内容
     */
    public void send(SendMsgVO sendMsg) {
        sendMsg(sendMsg.getTo(), sendMsg);
    }


    /**
     * 消息发送(最后发送, 在send方法中循环用户Id 列表依次发送消息给指定人)
     * <p>
     * // 消息发送（同步:getBasicRemote 异步:getAsyncRemote）
     * </P>
     *
     * @param userId  消息接收人ID , onlineUsers 的 key
     * @param sendMsg 消息内容
     */
    private boolean sendMsg(String userId, SendMsgVO sendMsg) {
        if (CLIENTS.containsKey(userId)) {
            OnlineUser onlineUser = CLIENTS.get(userId);
            Session session = onlineUser.getSession();
            if (session != null) {
                // 这里因为是提供发布订阅来发送信息, 在线程中存在同一个 session 发送存在问题，使用异步发送
                synchronized (this) {
                    try {
                        onlineUser.getSession().getBasicRemote().sendText(JSON.toJSONString(sendMsg));
                        log.info("websocket用户ID:{} 在线: 成功推送信息, 消息：{} ", userId, JSON.toJSONString(sendMsg.toString()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    // log.info("websocket用户ID:{} 在线: 成功推送信息, 消息：{} ", userId, JSON.toJSONString(sendMsg.toString()));

                }
            }
        } else {

        }
        return true;
    }

    /**
     * 组装 消息通知需要的内容格式
     *
     * @param title   通知标题
     * @param message 通知消息
     */
    private String assembleNoticeMsg(String title, String message) {
        return "true";
    }


    /**
     * @return 获取在线用户
     */
    public List<OnlineUser> getOnlineUserList(String userId) {
        return CLIENTS.values()
                .stream()
                .filter(i -> !i.getUserId().equals(userId))
                .collect(Collectors.toList());
    }
}